#include "network/socket_fd.hpp"

#include "base/log.hpp"

#include <cstring>
#include <errno.h>

#include <sys/socket.h>

namespace ibox::network {

SocketFd SocketFd::create_socket(int domain, int type, int protocol) {
    int fd = ::socket(domain, type, protocol);
    if (fd < 0) {
        LOG_DEBUG("create socket fail, errno: {}, {}", errno, strerror(errno));
        return SocketFd();
    }

    return SocketFd(fd);
}

SocketFd SocketFd::create_tcp_socket() {
    return create_socket(AF_INET, SOCK_STREAM, 0);
}

int SocketFd::connect(const struct sockaddr *addr, socklen_t addrlen) {
    int ret = ::connect(get(), addr, addrlen);
    if (ret < 0) {
        LOG_DEBUG("fail, errno: {}, {}", errno, strerror(errno));
    }
    return ret;
}

int SocketFd::bind(const struct sockaddr *addr, socklen_t addrlen) {
    int ret = ::bind(get(), addr, addrlen);
    if (ret < 0) {
        LOG_DEBUG("fail, errno: {}, {}", errno, strerror(errno));
    }
    return ret;
}

int SocketFd::listen(int backlog) {
    int ret = ::listen(get(), backlog);
    if (ret < 0) {
        LOG_DEBUG("fail, errno: {}, {}", errno, strerror(errno));
    }
    return ret;
}

int SocketFd::accept(struct sockaddr *addr, socklen_t *addrlen) {
    int ret = ::accept(get(), addr, addrlen);
    if (ret < 0) {
        LOG_DEBUG("fail, errno: {}, {}", errno, strerror(errno));
    }
    return ret;
}

ssize_t SocketFd::send(const void *data_ptr, size_t data_size, int flag) {
    ssize_t ret = ::send(get(), data_ptr, data_size, flag);
    if (ret < 0) {
        LOG_DEBUG("fail, errno: {}, {}", errno, strerror(errno));
    }
    return ret;
}

ssize_t SocketFd::recv(void *data_ptr, size_t data_size, int flag) {
    ssize_t ret = ::recv(get(), data_ptr, data_size, flag);
    if (ret < 0) {
        LOG_DEBUG("fail, errno: {}, {}", errno, strerror(errno));
    }
    return ret;
}

int SocketFd::shutdown(int howto) {
    int ret = ::shutdown(get(), howto);
    if (ret < 0) {
        LOG_DEBUG("fail, errno: {}, {}", errno, strerror(errno));
    }
    return ret;
}

bool SocketFd::get_socket_opt(int level,
                              int optname,
                              void *optval,
                              socklen_t *optlen) {
    if (::getsockopt(get(), level, optname, optval, optlen) != 0) {
        LOG_DEBUG(
            "fail, opt:%d, errno: {}, {}", optname, errno, strerror(errno));
        return false;
    }
    return true;
}

bool SocketFd::set_socket_opt(int level, int optname, int optval) {
    if (::setsockopt(get(), level, optname, &optval, sizeof(optval)) != 0) {
        LOG_DEBUG("fail, opt:{}, val:{}, errno: {}, {}",
                  optname,
                  optval,
                  errno,
                  strerror(errno));
        return false;
    }
    return true;
}

bool SocketFd::set_socket_opt(int level,
                              int optname,
                              const void *optval,
                              socklen_t optlen) {
    if (::setsockopt(get(), level, optname, optval, optlen) != 0) {
        LOG_DEBUG(
            "fail, opt:%d, errno: {}, {}", optname, errno, strerror(errno));
        return false;
    }
    return true;
}

bool SocketFd::set_reuse_address(bool enable) {
    return set_socket_opt(SOL_SOCKET, SO_REUSEADDR, static_cast<int>(enable));
}

bool SocketFd::set_broadcast(bool enable) {
    return set_socket_opt(SOL_SOCKET, SO_BROADCAST, static_cast<int>(enable));
}

bool SocketFd::set_keepalive(bool enable) {
    return set_socket_opt(SOL_SOCKET, SO_KEEPALIVE, static_cast<int>(enable));
}

bool SocketFd::set_recv_buffer_size(int size) {
    return set_socket_opt(SOL_SOCKET, SO_RCVBUF, size);
}

bool SocketFd::set_send_buffer_size(int size) {
    return set_socket_opt(SOL_SOCKET, SO_SNDBUF, size);
}

bool SocketFd::set_recv_low_water(int size) {
    return set_socket_opt(SOL_SOCKET, SO_RCVLOWAT, size);
}

bool SocketFd::set_send_low_water(int size) {
    return set_socket_opt(SOL_SOCKET, SO_SNDLOWAT, size);
}

bool SocketFd::set_linger(bool enable, int linger) {
    struct linger value
        = {.l_onoff = static_cast<int>(enable), .l_linger = linger};
    return set_socket_opt(SOL_SOCKET, SO_LINGER, &value, sizeof(value));
}

} // namespace ibox::network
